#include "filesystem-linux.h"
#include <cstdlib>
#include <dirent.h>
#include <sys/stat.h>
#include <cstring>
#include <cerrno>
#include <fcntl.h>
#include <poll.h>
#include "sys/socket.h"
#include <sys/vfs.h>
#include "android/log.h"
#include <jni.h>
#include <grp.h>
#include <pwd.h>
#include "sys/inotify.h"
#include "android/log.h"
#include "unistd.h"
#include "sys/sendfile.h"
#include "sys/statvfs.h"

// XD
#define Forever for(;;)

#define RETRY_ON_EINTR(exp) ({ \
    __typeof__(exp) _rc; \
    do { \
        errno = 0; \
        _rc = (exp); \
    } while (_rc == -1 && errno == EINTR); \
    if (_rc != -1) { \
        errno = 0; \
    } \
    _rc; })

#define EXCEPTION(env, name) throwErrnoException(env, name, errno)

#define PATH char *

#define MAX_EVENT_SIZE 1024
#define EVENT_SIZE (sizeof(struct inotify_event))
#define INOTIFY_BUFFER_SIZE (MAX_EVENT_SIZE * (EVENT_SIZE + NAME_MAX + 1))
// Fixed Size: 272
#define INOTIFY_EVENT_SIZE (EVENT_SIZE + NAME_MAX + 1)

typedef unsigned long long size_dir;

static char *fromByteArrayToPath(JNIEnv *env, jbyteArray path) {
    jbyte *segments = env->GetByteArrayElements(path, nullptr);
    jsize jLength = env->GetArrayLength(path);
    size_t length = jLength;
    char *cPath = (char *) malloc(length + 1);
    memcpy(cPath, segments, length);
    env->ReleaseByteArrayElements(path, segments, JNI_ABORT);
    cPath[length] = '\0';
    return cPath;
}

static jbyteArray createByteArray(JNIEnv *env, size_t len, const char *name) {
    jbyteArray bytes = env->NewByteArray((jsize) len);
    env->SetByteArrayRegion(bytes, 0, (jsize) len, (jbyte *) name);
    return bytes;
}

static jbyteArray createByteArray(JNIEnv *env, const char *name) {
    return createByteArray(env, strlen(name), name);
}

const char *fs_type_name(unsigned int f_type) {
    switch (f_type) {
        case TMPFS_MAGIC:
            return "tmpfs";
        case NFS_SUPER_MAGIC:
            return "nfs";
        case V9FS_MAGIC:
            return "9p";
        case RAMFS_MAGIC:
            return "ramfs";
        case BTRFS_SUPER_MAGIC:
            return "btrfs";
        case XFS_SUPER_MAGIC:
            return "xfs";
        case EXT2_OLD_SUPER_MAGIC:
            return "ext2";
        case EXT4_SUPER_MAGIC:
            return "ext2/ext3/ext4";
        case MINIX_SUPER_MAGIC2:
        case MINIX2_SUPER_MAGIC:
        case MINIX2_SUPER_MAGIC2:
        case MINIX3_SUPER_MAGIC:
            return "minix";
        case UDF_SUPER_MAGIC:
            return "udf";
        case SYSV2_SUPER_MAGIC:
        case SYSV4_SUPER_MAGIC:
            return "sysv";
        case UFS_MAGIC:
            return "ufs";
        case F2FS_SUPER_MAGIC:
            return "f2fs";
        case NILFS_SUPER_MAGIC:
            return "nilfs";
        case OVERLAYFS_SUPER_MAGIC:
            return "overlayfs";
        case FUSE_SUPER_MAGIC:
            return "fuse";
        default:
            return "unknown";
    }
}

void loadClass(JNIEnv *env, const char *name, jclass &ref) {
    jclass local = env->FindClass(name);
    ref = (jclass) env->NewGlobalRef(local);
    env->DeleteLocalRef(local);
}

void unloadClass(JNIEnv *env, jclass &ref) {
    env->DeleteGlobalRef(ref);
}

void loadMethod(JNIEnv *env, jclass ref_class, const char *name, const char *sig, jmethodID &ref) {
    ref = env->GetMethodID(ref_class, name, sig);
}

void throwErrnoException(JNIEnv *env, const char *name, int number) {
    throwErrnoException(env, name, number, strerror(number));
}

void throwErrnoException(JNIEnv *env, const char *name, int number, const char *desc) {
    if (!globalErrnoExceptionClass) {
        loadClass(
                env,
                ErrnoExceptionClass,
                globalErrnoExceptionClass
        );
    }
    if (!globalErrnoExceptionInitMethod) {
        loadMethod(
                env,
                globalErrnoExceptionClass,
                ErrnoExceptionConstructor,
                ErrnoExceptionConstructogSig,
                globalErrnoExceptionInitMethod
        );
    }

    auto exception = (jthrowable) env->NewObject(
            globalErrnoExceptionClass,
            globalErrnoExceptionInitMethod,
            env->NewStringUTF(name),
            number,
            env->NewStringUTF(desc)
    );

    env->Throw(exception);
}

extern "C"
JNIEXPORT jint JNICALL
Java_io_hellsinger_filesystem_linux_LinuxFileOperationProvider_accessImpl(
        JNIEnv *env,
        jobject thiz,
        jbyteArray path,
        jint mode
) {
    PATH cPath = fromByteArrayToPath(env, path);
    int result = access(cPath, mode) ? errno : 0;
    free(cPath);
    return result;
}

extern "C"
JNIEXPORT jlong JNICALL
Java_io_hellsinger_filesystem_linux_LinuxFileOperationProvider_allocateImpl__I(
        JNIEnv *env,
        jobject thiz,
        jint capacity
) {
    return (uintptr_t) malloc(capacity);
}

extern "C"
JNIEXPORT jlong JNICALL
Java_io_hellsinger_filesystem_linux_LinuxFileOperationProvider_allocateImpl__J(
        JNIEnv *env,
        jobject thiz,
        jlong capacity
) {
    return (uintptr_t) malloc(capacity);
}

extern "C"
JNIEXPORT void JNICALL
Java_io_hellsinger_filesystem_linux_LinuxFileOperationProvider_freeImpl(
        JNIEnv *env,
        jobject thiz,
        jlong pointer
) {
    free((void *) pointer);
}

extern "C"
JNIEXPORT jint JNICALL
Java_io_hellsinger_filesystem_linux_LinuxFileOperationProvider_openFileDescriptorImpl(
        JNIEnv *env,
        jobject thiz,
        jbyteArray path,
        jint flags,
        jint mode
) {

    PATH cPath = fromByteArrayToPath(env, path);
    int result = open64(cPath, flags, mode);
    free(cPath);

    if (result == -1) EXCEPTION(env, "openFileDescriptor");

    return result;
}

extern "C"
JNIEXPORT void JNICALL
Java_io_hellsinger_filesystem_linux_LinuxFileOperationProvider_closeFileDescriptorImpl(
        JNIEnv *env,
        jobject thiz,
        jint fd
) {
    RETRY_ON_EINTR(close(fd));
    if (errno) EXCEPTION(env, "closeFileDescriptor");
}

extern "C"
JNIEXPORT jlong JNICALL
Java_io_hellsinger_filesystem_linux_LinuxFileOperationProvider_openDirectoryPointerImpl(
        JNIEnv *env,
        jobject thiz,
        jbyteArray path
) {
    PATH cPath = fromByteArrayToPath(env, path);
    DIR *pointer = opendir(cPath);
    free(cPath);
    if (!pointer) EXCEPTION(env, "openDirectoryPointer");

    return (jlong) pointer;
}

extern "C"
JNIEXPORT jobject JNICALL
Java_io_hellsinger_filesystem_linux_LinuxFileOperationProvider_getDirectoryEntryImpl(
        JNIEnv *env,
        jobject thiz,
        jlong pointer
) {
    if (!pointer) return nullptr;

    if (!globalLinuxDirectoryEntryImplClass) {
        loadClass(
                env,
                LinuxDirectoryEntryImplClass,
                globalLinuxDirectoryEntryImplClass
        );
    }

    if (!globalLinuxDirectoryEntryImplInitMethod) {
        loadMethod(
                env,
                globalLinuxDirectoryEntryImplClass,
                LinuxDirectoryEntryImplConstructor,
                LinuxDirectoryEntryImplConstructorSig,
                globalLinuxDirectoryEntryImplInitMethod
        );
    }

    DIR *dir = (DIR *) pointer;
    struct dirent64 *entry = readdir64(dir);
    if (!entry) return nullptr;

    jbyteArray path = createByteArray(env, entry->d_name);
    jlong type = entry->d_type;
    jlong id = entry->d_ino;

    return env->NewObject(
            globalLinuxDirectoryEntryImplClass,
            globalLinuxDirectoryEntryImplInitMethod,
            path,
            id,
            type
    );
}

extern "C"
JNIEXPORT void JNICALL
Java_io_hellsinger_filesystem_linux_LinuxFileOperationProvider_closeDirectoryPointerImpl(
        JNIEnv *env,
        jobject thiz,
        jlong pointer
) {
    if (pointer == -1) EXCEPTION(env, "closeDirectoryPointer");
    if (closedir((DIR *) pointer)) EXCEPTION(env, "closeDirectoryPointer");
}

void get_dir_size_rec(const char *path, size_dir *totalSize) {
    struct dirent64 *entry;
    auto pointer = opendir(path);
    struct stat info{};
    for (entry = readdir64(pointer); entry != nullptr; entry = readdir64(pointer)) {
        if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, ".."))
            continue;

        char entryPath[PATH_MAX];
        strcpy(entryPath, path);
        strcat(entryPath, "/");
        strcat(entryPath, entry->d_name);
        if (stat(entryPath, &info) == -1) {
            perror("stat");
            return;
        }
        __android_log_print(ANDROID_LOG_INFO, "Loggable", "%s", entryPath);
        if (S_ISDIR(info.st_mode))
            get_dir_size_rec(entryPath, totalSize);
        else if (S_ISREG(info.st_mode))
            (*totalSize) += info.st_size;
    }
    closedir(pointer);
}

void get_dir_count_rec(const char *path, unsigned int *totalCount) {
    struct dirent64 *entry;
    auto pointer = opendir(path);
    for (entry = readdir64(pointer); entry != nullptr; entry = readdir64(pointer)) {
        if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, ".."))
            continue;

        char entryPath[PATH_MAX];
        strcpy(entryPath, path);
        strcat(entryPath, "/");
        strcat(entryPath, entry->d_name);
        struct stat info{};
        if (stat(entryPath, &info) == -1) {
            perror("stat");
            return;
        }
        if (S_ISDIR(info.st_mode)) {
            (*totalCount) += 1;
            get_dir_count_rec(entryPath, totalCount);
        } else if (S_ISREG(info.st_mode))
            (*totalCount) += 1;
    }
    closedir(pointer);
}

extern "C"
JNIEXPORT jlong JNICALL
Java_io_hellsinger_filesystem_linux_LinuxFileOperationProvider_getFileSizeImpl(
        JNIEnv *env,
        jobject thiz,
        jbyteArray path
) {
    PATH cPath = fromByteArrayToPath(env, path);
    struct stat64 buf{};
    int result = stat64(cPath, &buf);
    if (!result) EXCEPTION(env, "getFileSize");
    free(cPath);
    return buf.st_size;
}

extern "C"
JNIEXPORT jlong JNICALL
Java_io_hellsinger_filesystem_linux_LinuxFileOperationProvider_getDirectorySizeImpl(
        JNIEnv *env,
        jobject thiz,
        jbyteArray path
) {
    PATH cPath = fromByteArrayToPath(env, path);
    size_dir size = 0;
    get_dir_size_rec(cPath, &size);
    free(cPath);
    return (jlong) size;
}

extern "C"
JNIEXPORT jint JNICALL
Java_io_hellsinger_filesystem_linux_LinuxFileOperationProvider_getDirectoryTreeCountImpl(
        JNIEnv *env,
        jobject thiz,
        jbyteArray path
) {
    PATH cPath = fromByteArrayToPath(env, path);
    unsigned int count = 0;
    get_dir_count_rec(cPath, &count);
    free(cPath);
    return (jint) count;
}

extern "C"
JNIEXPORT jint JNICALL
Java_io_hellsinger_filesystem_linux_LinuxFileOperationProvider_getDirectoryCountImpl(
        JNIEnv *env,
        jobject thiz,
        jbyteArray path
) {
    PATH cPath = fromByteArrayToPath(env, path);
    DIR *pointer = opendir(cPath);
    if (!pointer) return -1;
    free(cPath);

    int count = 0;
    struct dirent64 *entry;
    while ((entry = readdir64(pointer))) {
        if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) continue;
        count++;
    }
    closedir(pointer);

    return count;
}

extern "C"
JNIEXPORT void JNICALL
Java_io_hellsinger_filesystem_linux_LinuxFileOperationProvider_makeDirectoryImpl(
        JNIEnv *env, jobject thiz,
        jbyteArray path,
        jint mode
) {
    PATH cPath = fromByteArrayToPath(env, path);
    if (mkdir(cPath, mode)) EXCEPTION(env, "makeDirectory");
    free(cPath);
}

extern "C"
JNIEXPORT void JNICALL
Java_io_hellsinger_filesystem_linux_LinuxFileOperationProvider_renameImpl(
        JNIEnv *env,
        jobject thiz,
        jbyteArray src,
        jbyteArray dest
) {
    PATH cSrcPath = fromByteArrayToPath(env, src);
    PATH cDestPath = fromByteArrayToPath(env, dest);

    if (rename(cSrcPath, cDestPath)) EXCEPTION(env, "rename");

    free(cSrcPath);
    free(cDestPath);
}

extern "C"
JNIEXPORT jobject JNICALL
Java_io_hellsinger_filesystem_linux_LinuxFileOperationProvider_getStatusImpl(
        JNIEnv *env,
        jobject thiz,
        jbyteArray path
) {
    if (!globalLinuxPathAttributeImplClass) {
        loadClass(
                env,
                LinuxPathAttributeImplClass,
                globalLinuxPathAttributeImplClass
        );
    }
    if (!globalLinuxFileSystemAttributeInitMethod) {
        loadMethod(
                env,
                globalLinuxPathAttributeImplClass,
                LinuxPathAttributeImplConstructor,
                LinuxPathAttributeImplConstructorSig,
                globalLinuxFileSystemAttributeInitMethod
        );
    }

    struct stat64 buff{};
    PATH cPath = fromByteArrayToPath(env, path);
    auto result = stat64(cPath, &buff);
    free(cPath);

    if (result) {
        if (errno == ENOENT) return nullptr;

        EXCEPTION(env, "getStatus");
    }

    return env->NewObject(
            globalLinuxPathAttributeImplClass,
            globalLinuxFileSystemAttributeInitMethod,
            (jint) buff.st_uid,
            (jint) buff.st_gid,
            (jint) buff.st_mode,
            (jlong) buff.st_ino,
            (jlong) buff.st_size,
            (jlong) buff.st_atim.tv_sec,
            (jlong) buff.st_atim.tv_nsec,
            (jlong) buff.st_mtim.tv_sec,
            (jlong) buff.st_mtim.tv_nsec,
            (jlong) buff.st_ctim.tv_sec,
            (jlong) buff.st_ctim.tv_nsec
    );
}

extern "C"
JNIEXPORT jobject JNICALL
Java_io_hellsinger_filesystem_linux_LinuxFileOperationProvider_getStatusAtDirImpl(
        JNIEnv *env,
        jobject thiz,
        jlong pointer,
        jbyteArray path
) {
    int desc = dirfd((DIR *) pointer);
    if (!globalLinuxPathAttributeImplClass) {
        loadClass(
                env,
                LinuxPathAttributeImplClass,
                globalLinuxPathAttributeImplClass
        );
    }
    if (!globalLinuxFileSystemAttributeInitMethod) {
        loadMethod(
                env,
                globalLinuxPathAttributeImplClass,
                LinuxPathAttributeImplConstructor,
                LinuxPathAttributeImplConstructorSig,
                globalLinuxFileSystemAttributeInitMethod
        );
    }

    struct stat64 buff{};
    PATH cPath = fromByteArrayToPath(env, path);
    fstatat64(desc, cPath, &buff, AT_SYMLINK_NOFOLLOW);
    free(cPath);

    return env->NewObject(
            globalLinuxPathAttributeImplClass,
            globalLinuxFileSystemAttributeInitMethod,
            (jint) buff.st_uid,
            (jint) buff.st_gid,
            (jint) buff.st_mode,
            (jlong) buff.st_ino,
            (jlong) buff.st_size,
            (jlong) buff.st_atim.tv_sec,
            (jlong) buff.st_atim.tv_nsec,
            (jlong) buff.st_mtim.tv_sec,
            (jlong) buff.st_mtim.tv_nsec,
            (jlong) buff.st_ctim.tv_sec,
            (jlong) buff.st_ctim.tv_nsec
    );
}

extern "C"
JNIEXPORT jobject JNICALL
Java_io_hellsinger_filesystem_linux_LinuxFileOperationProvider_getFileSystemStatusImpl(
        JNIEnv *env,
        jobject thiz,
        jbyteArray path
) {
    if (!globalLinuxFileSystemAttributeClass) {
        loadClass(
                env,
                LinuxFileSystemAttributeImplClass,
                globalLinuxFileSystemAttributeClass
        );
    }

    if (!globalLinuxFileSystemAttributeInitMethod) {
        loadMethod(
                env,
                globalLinuxFileSystemAttributeClass,
                LinuxFileSystemAttributeImplConstructor,
                LinuxFileSystemAttributeImplConstructorSig,
                globalLinuxFileSystemAttributeInitMethod
        );
    }

    PATH cPath = fromByteArrayToPath(env, path);
    struct statvfs buf;

    auto result = statvfs(cPath, &buf);
    free(cPath);
    if (result) EXCEPTION(env, "getFileSystemStatus");

    return env->NewObject(
            globalLinuxFileSystemAttributeClass,
            globalLinuxFileSystemAttributeInitMethod,
            (jlong) buf.f_bsize,
            (jlong) buf.f_frsize,
            (jlong) buf.f_blocks,
            (jlong) buf.f_bfree,
            (jlong) buf.f_bavail,
            (jlong) buf.f_files,
            (jlong) buf.f_ffree,
            (jlong) buf.f_favail,
            (jlong) buf.f_fsid,
            (jlong) buf.f_flag,
            (jlong) buf.f_namemax
    );
}

extern "C"
JNIEXPORT jstring JNICALL
Java_io_hellsinger_filesystem_linux_LinuxFileOperationProvider_getFileSystemNameImpl(
        JNIEnv *env,
        jobject thiz,
        jbyteArray path
) {
    PATH cPath = fromByteArrayToPath(env, path);
    struct statfs64 buf;
    auto result = statfs64(cPath, &buf);
    free(cPath);
    if (result) EXCEPTION(env, "getFileSystemStatus");
    return env->NewStringUTF(fs_type_name(buf.f_type));
}

extern "C"
JNIEXPORT jint JNICALL
Java_io_hellsinger_filesystem_linux_LinuxFileOperationProvider_initInotifyImpl(
        JNIEnv *env,
        jobject thiz,
        jint flags
) {
    int result = inotify_init1(flags);

    if (result == -1) EXCEPTION(env, "initInotify");

    return result;
}

extern "C"
JNIEXPORT jint JNICALL
Java_io_hellsinger_filesystem_linux_LinuxFileOperationProvider_addObservablePathImpl(
        JNIEnv *env,
        jobject thiz,
        jint fd,
        jbyteArray path,
        jint mask
) {
    PATH cPath = fromByteArrayToPath(env, path);
    int result = inotify_add_watch(fd, cPath, mask);
    free(cPath);

    if (result == -1) EXCEPTION(env, "addObservablePath");

    return result;
}

extern "C"
JNIEXPORT jint JNICALL
Java_io_hellsinger_filesystem_linux_LinuxFileOperationProvider_readImpl__IIJI(
        JNIEnv *env,
        jobject thiz,
        jint fd,
        jint position,
        jlong buffer,
        jint count
) {
    return read(fd, (void *) (buffer + position), count);
}

extern "C"
JNIEXPORT jint JNICALL
Java_io_hellsinger_filesystem_linux_LinuxFileOperationProvider_readImpl__IIJIJ(
        JNIEnv *env,
        jobject thiz,
        jint fd,
        jint position,
        jlong buffer,
        jint count,
        jlong offset
) {
    return pread64(fd, (void *) (buffer + position), count, offset);
}

extern "C"
JNIEXPORT jint JNICALL
Java_io_hellsinger_filesystem_linux_LinuxFileOperationProvider_writeImpl(
        JNIEnv *env,
        jobject thiz,
        jint fd,
        jlong buffer,
        jint count
) {
    return write(fd, (void *) buffer, count);
}

extern "C"
JNIEXPORT void JNICALL
Java_io_hellsinger_filesystem_linux_LinuxFileOperationProvider_unlinkImpl(
        JNIEnv *env,
        jobject thiz,
        jbyteArray path
) {
    PATH cPath = fromByteArrayToPath(env, path);
    if (unlink(cPath)) EXCEPTION(env, "unlink");
    free(cPath);
}

extern "C"
JNIEXPORT void JNICALL
Java_io_hellsinger_filesystem_linux_LinuxFileOperationProvider_unlinkAtDirImpl(
        JNIEnv *env,
        jobject thiz,
        jlong pointer,
        jbyteArray path,
        jint flags
) {
    PATH cPath = fromByteArrayToPath(env, path);
    if (!unlinkat(pointer, cPath, flags))EXCEPTION(env, "unlinkAtDir");
    free(cPath);
}

extern "C"
JNIEXPORT void JNICALL
Java_io_hellsinger_filesystem_linux_LinuxFileOperationProvider_removeDirectoryImpl(
        JNIEnv *env,
        jobject thiz,
        jbyteArray path
) {
    PATH cPath = fromByteArrayToPath(env, path);
    if (rmdir(cPath)) EXCEPTION(env, "removeDirectory");
    free(cPath);
}

extern "C"
JNIEXPORT void JNICALL
Java_io_hellsinger_filesystem_linux_LinuxFileOperationProvider_removeObservablePathImpl(
        JNIEnv *env,
        jobject thiz,
        jint fd,
        jint wd
) {
    if (inotify_rm_watch(fd, wd)) EXCEPTION(env, "removeObservablePath");
}

extern "C"
JNIEXPORT jobject JNICALL
Java_io_hellsinger_filesystem_linux_LinuxFileOperationProvider_pollEventImpl(
        JNIEnv *env,
        jobject thiz,
        jint fd
) {
    if (!globalLinuxObservablePathImplClass) {
        loadClass(
                env,
                LinuxObservablePathImplClass,
                globalLinuxObservablePathImplClass
        );
    }

    if (!globalLinuxObservablePathImplInitMethod) {
        loadMethod(
                env,
                globalLinuxObservablePathImplClass,
                LinuxObservablePathImplConstructor,
                LinuxObservablePathImplConstructorSig,
                globalLinuxObservablePathImplInitMethod
        );
    }

    struct pollfd fds[1];
    fds[0].fd = fd;
    fds[0].events = POLLIN;

    Forever {
        int count = poll(fds, 1, -1);
        if (count <= 0) continue;
        if (!(fds[0].revents & POLLIN)) continue;

        // Now we sure we can work with our events!
        char buf[INOTIFY_EVENT_SIZE];
        ssize_t len = read(fd, buf, INOTIFY_EVENT_SIZE);
        if (len == -1 && errno != EAGAIN) EXCEPTION(env, "pollEvents");

        if (len > 0) {
            auto ptr = (const struct inotify_event *) buf;
            jint cookie = ptr->cookie;
            jint mask = ptr->mask;
            jint wd = ptr->wd;
            jbyteArray name = createByteArray(env, ptr->len, ptr->name);

            return env->NewObject(
                    globalLinuxObservablePathImplClass,
                    globalLinuxObservablePathImplInitMethod,
                    cookie,
                    mask,
                    wd,
                    name
            );
        }
    };
}

extern "C"
JNIEXPORT jint JNICALL
Java_io_hellsinger_filesystem_linux_LinuxFileOperationProvider_sendBytesImpl___3B_3B(
        JNIEnv *env,
        jobject thiz,
        jbyteArray from,
        jbyteArray to
) {
    PATH fromPath = fromByteArrayToPath(env, from);
    PATH toPath = fromByteArrayToPath(env, to);

    int fromDescriptor = open64(fromPath, O_NONBLOCK | O_RDONLY);
    if (fromDescriptor == -1) EXCEPTION(env, "sendBytes");
    free(fromPath);
    struct stat64 stat{};
    if (fstat64(fromDescriptor, &stat) == -1) EXCEPTION(env, "sendBytes");
    int toDescriptor = open64(toPath, O_NONBLOCK | O_WRONLY | O_CREAT, stat.st_mode);
    if (toDescriptor == -1) EXCEPTION(env, "sendBytes");
    free(toPath);

    int bytes = sendfile64(
            toDescriptor,
            fromDescriptor,
            nullptr,
            stat.st_size
    );

    close(fromDescriptor);
    close(toDescriptor);

    return bytes;
}

extern "C"
JNIEXPORT jint JNICALL
Java_io_hellsinger_filesystem_linux_LinuxFileOperationProvider_sendBytesImpl___3B_3BJI(
        JNIEnv *env,
        jobject thiz,
        jbyteArray from,
        jbyteArray to,
        jlong offset,
        jint count
) {
    PATH fromPath = fromByteArrayToPath(env, from);
    PATH toPath = fromByteArrayToPath(env, to);

    int fromDescriptor = open64(fromPath, O_NONBLOCK | O_RDONLY);
    if (fromDescriptor == -1) EXCEPTION(env, "sendBytes");
    free(fromPath);
    struct stat64 stat{};

    if (fstat64(fromDescriptor, &stat) == -1) EXCEPTION(env, "sendBytes");
    int toDescriptor = open64(toPath, O_NONBLOCK | O_WRONLY | O_CREAT, stat.st_mode);
    if (toDescriptor == -1) EXCEPTION(env, "sendBytes");
    free(toPath);

    return sendfile64(toDescriptor, fromDescriptor, &offset, count);
}

extern "C"
JNIEXPORT jint JNICALL
Java_io_hellsinger_filesystem_linux_LinuxFileOperationProvider_sendBytesImpl__IILjava_lang_Long_2I(
        JNIEnv *env, jobject thiz,
        jint from,
        jint to,
        jobject offset,
        jint count
) {
    off64_t *off = nullptr;
    if (offset) off = (jlong *) &offset;
    return sendfile64(to, from, off, count);
}

extern "C"
JNIEXPORT jbyte JNICALL
Java_io_hellsinger_filesystem_linux_LinuxFileOperationProvider_getByteImpl(
        JNIEnv *env,
        jobject thiz,
        jlong buffer,
        jint index
) {
    return *(jbyte *) (buffer + index);
}

extern "C"
JNIEXPORT void JNICALL
Java_io_hellsinger_filesystem_linux_LinuxFileOperationProvider_changeModeImpl(
        JNIEnv *env,
        jobject thiz,
        jbyteArray path,
        jint mode
) {
    PATH cPath = fromByteArrayToPath(env, path);
    int result = chmod(cPath, mode);
    if (!result) EXCEPTION(env, "changeMode");
    free(cPath);
}

extern "C"
JNIEXPORT jobject JNICALL
Java_io_hellsinger_filesystem_linux_LinuxFileOperationProvider_getUserEntryImpl(
        JNIEnv *env,
        jobject thiz,
        jint user_id
) {
    if (!globalLinuxUserEntryImplClass) {
        loadClass(
                env,
                LinuxUserEntryImplClass,
                globalLinuxUserEntryImplClass
        );
    }

    if (!globalLinuxUserEntryImplInitMethod) {
        loadMethod(
                env,
                globalLinuxUserEntryImplClass,
                LinuxUserEntryImplConstructor,
                LinuxUserEntryImplConstructorSig,
                globalLinuxUserEntryImplInitMethod
        );
    }

    struct passwd *result = getpwuid(user_id);

    jint id = result->pw_uid;
    jbyteArray name = createByteArray(env, result->pw_name);
    // Check if gr_passwd references to nullptr (if so return empty password)
    jbyteArray password = createByteArray(env, !result->pw_passwd ? "\0" : result->pw_passwd);
    jbyteArray home = createByteArray(env, !result->pw_dir ? "\0" : result->pw_dir);
    jbyteArray shell = createByteArray(env, result->pw_shell ? "\0" : result->pw_shell);

    return env->NewObject(
            globalLinuxUserEntryImplClass,
            globalLinuxUserEntryImplInitMethod,
            id,
            name,
            password,
            home,
            shell
    );
}

extern "C"
JNIEXPORT jobject JNICALL
Java_io_hellsinger_filesystem_linux_LinuxFileOperationProvider_getGroupEntryImpl(
        JNIEnv *env,
        jobject thiz,
        jint group_id
) {
    if (!globalLinuxGroupEntryImplClass) {
        loadClass(
                env,
                LinuxGroupEntryImplClass,
                globalLinuxGroupEntryImplClass
        );
    }

    if (!globalLinuxGroupEntryImplInitMethod) {
        loadMethod(
                env,
                globalLinuxGroupEntryImplClass,
                LinuxGroupEntryImplConstructor,
                LinuxGroupEntryImplConstructorSig,
                globalLinuxGroupEntryImplInitMethod
        );
    }

    struct group *result = getgrgid(group_id);
    if (!result) EXCEPTION(env, "getGroupEntry");
    jint id = result->gr_gid;
    jbyteArray name = createByteArray(env, result->gr_name);
    // Check if gr_passwd references to nullptr (if so return empty password)
    jbyteArray password = createByteArray(env, !result->gr_passwd ? "\0" : result->gr_passwd);

    return env->NewObject(
            globalLinuxGroupEntryImplClass,
            globalLinuxGroupEntryImplInitMethod,
            id,
            name,
            password
    );
}


extern "C"
JNIEXPORT void JNICALL
Java_io_hellsinger_filesystem_linux_LinuxFileOperationProvider_createSymbolicLinkImpl(
        JNIEnv *env,
        jobject thiz,
        jbyteArray source,
        jbyteArray path
) {
    PATH cSource = fromByteArrayToPath(env, source);
    PATH cPath = fromByteArrayToPath(env, path);

    if (symlink(cSource, cPath)) EXCEPTION(env, "createSymbolicLink");

    free(cSource);
    free(cPath);
}

extern "C"
JNIEXPORT void JNICALL
Java_io_hellsinger_filesystem_linux_LinuxFileOperationProvider_createLinkImpl(
        JNIEnv *env,
        jobject thiz,
        jbyteArray source,
        jbyteArray path
) {
    PATH cSource = fromByteArrayToPath(env, source);
    PATH cPath = fromByteArrayToPath(env, path);

    if (symlink(cSource, cPath)) EXCEPTION(env, "createSymbolicLink");

    free(cSource);
    free(cPath);
}
extern "C"
JNIEXPORT jlong JNICALL
Java_io_hellsinger_filesystem_linux_LinuxFileOperationProvider_seekAtImpl(
        JNIEnv *env,
        jobject thiz,
        jint fd,
        jlong offset,
        jint type
) {
    jlong newOffset = lseek64(fd, offset, type);
    if (newOffset == -1) EXCEPTION(env, "repositionFileOffset");
    return newOffset;
}

extern "C"
JNIEXPORT jlong JNICALL
Java_io_hellsinger_filesystem_linux_LinuxFileOperationProvider_fillMemoryImpl(
        JNIEnv *env,
        jobject thiz,
        jlong pointer,
        jint from,
        jint placeholder,
        jint count
) {
    return (uintptr_t) memset((void *) (pointer + from), placeholder, sizeof(jint));
}

extern "C"
JNIEXPORT void JNICALL
Java_io_hellsinger_filesystem_linux_LinuxFileOperationProvider_setByteImpl(
        JNIEnv *env,
        jobject thiz,
        jlong buffer, jint index,
        jbyte byte
) {
    *(jbyte *) (buffer + index) = byte;
}